<?php
/*

  Copyright (c) 2008   by  maxim.oransky <maxim.oransky@gmail.com>,
  auhtor of  the-wheel-library  team

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

class RssExporterItem {

	private $itemTitle = false;
	/**
	 * The title of the item.
	 */
	public function setTitle($str) { $this->itemTitle = $str; }

	private $itemLink = false;
	/**
	 * The URL of the item.
	 */
	public function setLink($url) { $this->itemLink = $url; }

	private $itemDescription = false;
	/**
	 * The item synopsis.
	 */
	public function setDescription($str) { $this->itemDescription = $str; }

	private $itemAuthor = false;
	/**
	 * Email address of the author of the item.
	 */
	public function setAuthor($email) { $this->itemAuthor = $email; }

	private $arrItemCategoryes = array();
	/**
	 * Includes the item in one or more categories.
	 */
	public function addItemCategory($str) {
		$this->arrItemCategoryes[] = $str;
	}

	private $itemComments = false;
	/**
	 * URL of a page for comments relating to the item.
	 */
	public function setComments($url) { $this->itemComments = $url; }

	private $arrItemEnclosures = array();
	/**
	 * Describes a media object that is attached to the item.
	 */
	public function addItemEnclosure($url, $length, $type) {
		$this->arrItemEnclosure[] = array(
			'url' => $url,
			'length' => $length,
			'type' => $type,
		);
	}

	private $itemGuid = false;
	/**
	 * A string that uniquely identifies the item. More.	http://inessential.com/2002/09/01.php#a2
	 */
	public function setGuid($str) { $this->itemGuid = $str; }

	private $itemPubDate = false;
	/**
	 * Indicates when the item was published.
	 */
	public function setPubDate($timestamp) { $this->itemPubDate = date('r',$timestamp); }

	private $itemSourceUrl = false;
	private $itemSourceTitle = false;
	/**
	 * The RSS channel that the item came from.
	 */
	public function setSource($url, $title) {
		$this->itemSourceUrl = $url;
		$this->itemSourceTitle = $title;
	}

	private function checkRequiredElementsSet() {
		if (!$this->itemTitle)
			throw new Exception('item title not setted');

		if (!$this->itemLink)
			throw new Exception('item link not setted');

		if (!$this->itemDescription)
			throw new Exception('item description not setted');
	}

	public function export() {

		$this->checkRequiredElementsSet();

		if (!$this->itemGuid) $this->itemGuid = $this->itemLink;
		$str = '<item>' .
			XmlUtils::tag('title', $this->itemTitle) .
			XmlUtils::tag('link', $this->itemLink) .
			XmlUtils::tag('description', $this->itemDescription) .
			XmlUtils::tag('author', $this->itemAuthor) .
			XmlUtils::arrayOfSimpleTags('category', $this->arrItemCategoryes) .
			XmlUtils::tag('comments', $this->itemComments) .
			XmlUtils::arrayOfAttrTags('enclosure', $this->arrItemEnclosures) .
			XmlUtils::tag('guid', $this->itemGuid) .
			XmlUtils::tag('pubDate', $this->itemPubDate) .
			XmlUtils::tag('source', $this->itemSourceTitle, array(
				'url' => $this->itemSourceUrl
			)) .
		'</item>';

		return $str;
	}
}

class RssExporter {

	private $encoding = 'UTF-8';

	public function __construct($encoding = false) {

		if ($encoding) $this->encoding = $encoding;

		$this->setGenerator('The Wheel Library RSS composer');
		$this->setDocs('http://blogs.law.harvard.edu/tech/rss');
		$this->setLastBuildDate(time());
	}

	private $arrItems = array();
	/**
	 * @return RssExporterItem
	 */
	public function createItem() {
		$item = new RssExporterItem();
		$this->arrItems[] = $item;
		return $item;
	}

	public function composeXml() {

		$this->checkRequiredChannelElementsSet();

		$sItems = '';
		foreach ($this->arrItems as $oRssItem) {
			$sItems .= $oRssItem->export();
		}

		$str = '<?xml version="1.0" encoding="' .$this->encoding .  '"?>' .
			'<rss version="2.0">' .
				'<channel>' .
					XmlUtils::tag('title', $this->channelTitle) .
					XmlUtils::tag('link', $this->channelLink) .
					XmlUtils::tag('description', $this->channelDescription) .
					XmlUtils::tag('language', $this->channelLanguage) .
					XmlUtils::tag('copyright', $this->channelCopyright) .
					XmlUtils::tag('managingEditor', $this->channelManagingEditor) .
					XmlUtils::tag('webMaster', $this->channelWebMaster) .
					XmlUtils::tag('pubDate', $this->channelPubDate) .
					XmlUtils::tag('lastBuildDate', $this->channelLastBuildDate) .
					XmlUtils::arrayOfSimpleTags('category', $this->arrChannelCategoryes) .
					XmlUtils::tag('generator', $this->channelGenerator) .
					XmlUtils::tag('docs', $this->channelDocs) .
					XmlUtils::tag('cloud', $this->channelCloud) .
					XmlUtils::tag('ttl', $this->channelTtl) .
					XmlUtils::setOfSimpleTags('image', array(
						'height' => $this->channelImageHeight,
						'width'  => $this->channelImageWidth,
						'url'    => $this->channelImageUrl,
						'link'   => $this->channelLink,
						'title'  => $this->channelTitle,
					)) .
					XmlUtils::tag('rating', $this->channelRating) .
					XmlUtils::tag('textInput', $this->channelTextInput) .
					XmlUtils::tag('skipHours', $this->channelSkipHours) .
					XmlUtils::tag('skipDays', $this->channelSkipDays) .
					$sItems .
				'</channel>' .
			'</rss>';

		return $str;
	}

	private $channelTitle = false;
	/**
	 * The name of the channel. It's how people refer to your service.
	 * If you have an HTML website that contains the same information as
	 * your RSS file, the title of your channel should be the same as the
	 * title of your website.
	 */
	public function setTitle($str) { $this->channelTitle = $str; }

	private $channelLink = false;
	/**
	 * The URL to the HTML website corresponding to the channel.
	 */
	public function setLink($str) { $this->channelLink = $str; }

	private $channelDescription = false;
	/**
	 * Phrase or sentence describing the channel.
	 */
	public function setDescription($str) { $this->channelDescription = $str; }

	private $channelLanguage = false;
	/**
	 * The language the channel is written in. This allows aggregators to group
	 * all Italian language sites, for example, on a single page. A list of
	 * allowable values for this element
	 *  http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
	 */
	public function setLanguage($str) { $this->channelLanguage = $str; }

	private $channelCopyright = false;
	/**
	 * Copyright notice for content in the channel.	Copyright 2002, Spartanburg Herald-Journal
	 */
	public function setCopyright($str) { $this->channelCopyright = $str; }

	private $channelManagingEditor = false;
	/**
	 * Email address for person responsible for editorial content.
	 */
	public function setManagingEditor($str) { $this->channelManagingEditor = $str; }

	private $channelWebMaster = false;
	/**
	 * Email address for person responsible for technical issues relating to channel.
	 */
	public function setWebMaster($str) { $this->channelWebMaster = $str; }

	private $channelPubDate = false;
	/**
	 * The publication date for the content in the channel. For example,
	 * the New York Times publishes on a daily basis, the publication date
	 * flips once every 24 hours. That's when the pubDate of the channel changes.
	 * All date-times in RSS conform to the Date and Time Specification of RFC 822,
	 * with the exception that the year may be expressed with two characters
	 * or four characters (four preferred).
	 */
	public function setPubDate($timestamp) { $this->channelPubDate = date('r', $timestamp); }

	private $channelLastBuildDate = false;
	/**
	 * The last time the content of the channel changed.
	 * Sat, 07 Sep 2002 09:42:31 GMT
	 */
	private function setLastBuildDate($timestamp) { $this->channelLastBuildDate = date('r', $timestamp); }

	private $arrChannelCategoryes = array();
	/**
	 * Specify one or more categories that the channel belongs to. Follows the same
	 * rules as the <item>-level category element.
	 */
	public function addCategory($str) {
		$this->arrChannelCategoryes[] = $str;
	}

	private $channelGenerator = false;
	/**
	 * A string indicating the program used to generate the channel.
	 */
	private function setGenerator($str) { $this->channelGenerator = $str; }

	private $channelDocs = false;
	/**
	 * A URL that points to the documentation for the format used in the RSS file.
	 * It's probably a pointer to this page. It's for people who might stumble
	 * across an RSS file on a Web server 25 years from now and wonder what
	 * it is.
	 */
	private function setDocs($str) { $this->channelDocs = $str; }

	private $channelCloud = false;
	/**
	 * Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds. More info here.	<cloud domain="rpc.sys.com" port="80" path="/RPC2" registerProcedure="pingMe" protocol="soap"/>
	 */
	public function setCloud(array $hash) { $this->channelCloud = $hash; }

	private $channelTtl = false;
	/**
	 * ttl stands for time to live. It's a number of minutes that indicates how long a
	 * channel can be cached before refreshing from the source.
	 */
	public function setTtl($num) { $this->channelTtl = $num; }

	private $channelImageUrl = false;
	private $channelImageWidth = false;
	private $channelImageHeight = false;
	/**
	 * Specifies a GIF, JPEG or PNG image that can be displayed with the channel..
	 */
	public function setImage($imageUrl, $imageFile) {

		$this->channelImageUrl = $imageUrl;

		list($width, $height, $type, $attr) = getimagesize($imageFile);
		$this->channelImageWidth = $width;
		$this->channelImageHeight = $height;
	}

	private $channelRating = false;
	/**
	 * The PICS rating for the channel.
	 * http://www.w3.org/PICS/
	 */
	public function setRating($str) { $this->channelRating = $str; }

	private $channelTextInput = false;
	/**
	 * Specifies a text input box that can be displayed with the channel.
	 */
	public function setTextInput($str) { $this->channelTextInput = $str; }

	private $channelSkipHours = false;
	/**
	 * A hint for aggregators telling them which hours they can skip.
	 */
	public function setSkipHours($num) { $this->channelSkipHours = $num; }

	private $channelSkipDays = false;
	/**
	 * A hint for aggregators telling them which days they can skip.
	 */
	public function setSkipDays($num) { $this->channelSkipDays = $num; }

	private function checkRequiredChannelElementsSet() {
		if (!$this->channelTitle)
			throw new Exception('channel title not setted');

		if (!$this->channelLink)
			throw new Exception('channel link not setted');

		if (!$this->channelDescription)
			throw new Exception('channel description not setted');
	}
}

class XmlUtils {

	public static function arrayOfSimpleTags($tagName, array $arr) {
		$str = '';
		foreach ($arr as $item) {
			$str .= self::tag($tagName, $item);
		}
		return $str;
	}

	public static function arrayOfAttrTags($tagName, array $arr) {
		$str = '';
		foreach ($arr as $item) {
			$str .= self::tag($tagName, '', $item);
		}
		return $str;
	}

	public static function setOfSimpleTags($tagName, array $hItem) {
		$str = '';
		foreach ($hItem as $tagName => $content) {
			if ($content === false) return '';
			$str .= self::tag($tagName, $content);
		}
		return "<$tagName>$str</$tagName>";;
	}

	public static function tag($tagName, $var, array $arrAttr = array()) {

		if ($var === false) return '';

		$sAttr = '';
		foreach ($arrAttr as $attrName => $attrVal) {
			$sAttr .= ' ' . $attrName . '="' . strtr(self::escapeXml($attrVal),'"', '\"') . '"';
		}

		return $var ?
			'<' . $tagName . $sAttr . '>' . self::escapeXml($var) . '</' . $tagName . '>' :
			'<' . $tagName . $sAttr . '/>';
	}

	private static function escapeXml($str) {
		return strtr($str, array(
			'&' => '&amp;',
			'<' => '&lt;',
			'>' => '&gt;',
		));
	}
}